package Storage;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.ParseException;

import org.json.JSONArray;
import org.json.JSONObject;

import Object.DSException;
import Task.DSTask;
import Task.DSTaskList;

//@author A0103535R
/************************************ Abstract Class DSDataFileHandler ***************************/
public class DSDataFileHandler {
	// Keys of value
	private static final String TASK_ID = "Id";
	private static final String TASK_NAME = "Name";
	private static final String TASK_START_DATE = "StartDate";
	private static final String TASK_END_DATE = "EndDate";
	private static final String TASK_START_TIME = "StartTime";
	private static final String TASK_END_TIME = "EndTime";
	private static final String TASK_IS_DONE = "isDone";
	private static final String TASK_TAGS = "Tags";
	private static final String DATA_FILE_PATH = "doSomething.txt";
	
	private static DSDataFileHandler _dataFileHandler = null;
	private File _dataFile = null;
	private BufferedWriter _dataBufferedWriter = null;
	private BufferedReader _dataBufferedReader = null;
	
	/**
	 * This is the constructor of DSFileHandler
	 * 
	 * @throws DSException
	 */
	private DSDataFileHandler() throws DSException {
		if (_dataFile == null) {
			initDataFile();
		}
		if (_dataBufferedReader == null) {
			initDataBufferedReader();
		}
		assert _dataFile != null;
		assert _dataBufferedReader != null;
	}

	/**
	 * This function is used to get the instance singleton of DSFileHandler
	 * 
	 * @return the singleton instance of DSFileHandler
	 * 
	 * @throws DSException
	 */
	public static DSDataFileHandler getInstance() throws DSException {
		if (_dataFileHandler == null) {
			_dataFileHandler = new DSDataFileHandler();
		}
		assert _dataFileHandler != null;
		return _dataFileHandler;
	}

	/**
	 * This function is used to initialize the data file.
	 * 
	 * @throws DSException
	 */
	private void initDataFile() throws DSException{
		try {
			_dataFile = new File(DATA_FILE_PATH);
			if (!_dataFile.exists()) {
				if (!_dataFile.createNewFile()) {
					throw new IOException();
				}
			}
			assert _dataFile != null;
		} catch (IOException e) {
			throw new DSException(DSException.EXCEPTION_MESSAGE_DSDATAFILEHANDLER_INIT_FILE);
		}
	}
	
	/**
	 * This function is used to initialize the buffered reader
	 * 
	 * @throws DSException
	 */
	private void initDataBufferedReader() throws DSException {
		try {
			_dataBufferedReader = new BufferedReader(new InputStreamReader(
					new FileInputStream(_dataFile)));
			assert _dataBufferedReader != null;
		} catch (FileNotFoundException e) {
			throw new DSException(DSException.EXCEPTION_MESSAGE_DSDATAFILEHANDLER_INIT_BUFFERED_READER);
		}
	}

	/**
	 * This function is used to initialize the buffered writer
	 * 
	 * @throws DSException
	 */
	private void initDataBufferedWriter() throws DSException {
		try {
			_dataBufferedWriter = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream(_dataFile, false)));
			assert _dataBufferedWriter != null;
		} catch (FileNotFoundException e) {
			throw new DSException(DSException.EXCEPTION_MESSAGE_DSDATAFILEHANDLER_INIT_BUFFERED_WRITER);
		}
	}

	/**
	 * This function is used to re-initialize the buffered writer
	 * 
	 * @throws DSException
	 */
	private void recreateDataBufferedWriter() throws DSException {
		initDataBufferedWriter();
	}
	
	/**
	 * This function is used to store the tasks in local file.
	 * 
	 * @param taskList
	 *            The task list to store in local file.
	 * 
	 * @return result of process.
	 * 
	 * @throws DSException
	 */
	public boolean saveToDataFile(DSTaskList taskList) throws DSException {
		if (taskList == null) {
			return false;
		}
		writeToDataFile(taskList);
		return true;
	}

	/**
	 * This function is used to write the tasks in local file.
	 * 
	 * @param taskList
	 *            The task list to write in local file.
	 *            
	 * @throws DSException
	 */
	private void writeToDataFile(DSTaskList taskList) throws DSException {
		JSONArray jsonArray = convertToJSONArray(taskList);
		recreateDataBufferedWriter();
		writeJSONArrayToDataFile(jsonArray);
	}

	/**
	 * This function is used to write JsonArray of tasks to local file.
	 * 
	 * @param jsonArray
	 *            the JsonArray form of tasks list.
	 *            
	 * @throws DSException
	 */
	private void writeJSONArrayToDataFile(JSONArray jsonArray) throws DSException {
		try {
			for (int i = 0; i < jsonArray.length(); i++) {
				_dataBufferedWriter.write(jsonArray.get(i).toString() + "\n");
			}
			_dataBufferedWriter.flush();
		} catch (IOException e) {
			throw new DSException(DSException.EXCEPTION_MESSAGE_DSDATAFILEHANDLER_WRITE_JSONARRAY_TO_FILE);
		}
	}

	/**
	 * This function is used to load the tasks from local file
	 *
	 * @return task list stored in local file
	 * 
	 * @throws DSException
	 */
	public DSTaskList loadFromDataFile() throws DSException {
		DSTaskList taskList = new DSTaskList();
		try {
			String lineString = _dataBufferedReader.readLine();
			while (lineString != null) {
				taskList.add(convertToTask(lineString));
				lineString = _dataBufferedReader.readLine();
			}
		} catch (IOException e) {
			throw new DSException(DSException.EXCEPTION_MESSAGE_DSDATAFILEHANDLER_LOAD_FROM_FILE);
		}
		assert taskList != null;
		return taskList;
	}

	/**
	 * This function is used to convert task list to JsonArray
	 * 
	 * @param taskList
	 * 			the task list to transform.
	 * 
	 * @return the JsonArray transformed from task list.
	 */
	private JSONArray convertToJSONArray(DSTaskList taskList) {
		JSONArray jsonArray = new JSONArray();
		for (int i = 0; i < taskList.size(); i++) {
			JSONObject object = convertToJSONObject(taskList.get(i));
			jsonArray.put(object);
		}
		return jsonArray;
	}

	/**
	 * This function is used to convert the task to JsonObject
	 * 
	 * @param task
	 *            The task to transform
	 * 
	 * @return the JsonObject transformed from task.
	 */
	private JSONObject convertToJSONObject(DSTask task) {
		JSONObject jsonObject = new JSONObject();
		jsonObject.put(TASK_ID, task.getTaskId());
		jsonObject.put(TASK_NAME, task.getTaskName());
		if (task.getTaskStartDateString() == "") {
			jsonObject.put(TASK_START_DATE, DSTask.DEFAULT_DATE);
		} else {
			jsonObject.put(TASK_START_DATE, task.getTaskStartDateString());
		}
		if (task.getTaskEndDateString() == "") {
			jsonObject.put(TASK_END_DATE, DSTask.DEFAULT_DATE);
		} else {
			jsonObject.put(TASK_END_DATE, task.getTaskEndDateString());
		}
		jsonObject.put(TASK_START_TIME, task.getTaskStartTime());
		jsonObject.put(TASK_END_TIME, task.getTaskEndTime());
		jsonObject.put(TASK_IS_DONE, task.isDone());
		jsonObject.put(TASK_TAGS, new JSONArray(task.getTaskTags()));
		return jsonObject;
	}

	/**
	 * This function is used to convert string to task
	 * 
	 * @param str
	 *            String read from local file
	 * 
	 * @return the task parsed from string.
	 * 
	 * @throws DSException
	 */
	private DSTask convertToTask(String str) throws DSException {
		try {
			JSONObject object = new JSONObject(str);
			DSTask task = null;
			task = new DSTask();
			task.setTaskId(object.getString(TASK_ID));
			task.setTaskName(object.getString(TASK_NAME));
			task.setTaskStartDate(object.getString(TASK_START_DATE));
			task.setTaskEndDate(object.getString(TASK_END_DATE));
			task.setTaskStartTime(object.getInt(TASK_START_TIME));
			task.setTaskEndTime((object.getInt(TASK_END_TIME)));
			task.setIsDone((object.getBoolean(TASK_IS_DONE)));
			if (object.getJSONArray(TASK_TAGS).length() != 0) {
				task.setTags((object.getJSONArray(TASK_TAGS).join(",")
						.replace("\"", "").split(",")));
			}
			return task;
		} catch (ParseException e) {
			throw new DSException(DSException.EXCEPTION_MESSAGE_DSDATAFILEHANDLER_CONVERT_TO_TASK);
		}
	}
}